﻿using UnityEngine;
using System.Collections;
using System;
using Networking;

public class SkeletonControl : MonoBehaviour {

    Vector3 movementDirection = Vector3.zero;
    public float moveSpeed = 8.0f;

    bool deadPlayed = false;
    bool attackPlayed = false;
    bool gethitPlayed = false;

    GameObject target = null;

    int largestPlayerCount = 1;

    public int seekRandomizer = 4;

    public enum SkeletonState
    {
        Waiting,
        Chasing,
        Fighting,
        Hit,
        Attacking,
        Dying,
        Dead
    }

    public SkeletonState currentState = SkeletonState.Waiting;
    private DateTime stateExpiration = DateTime.Now;

    DelayedInterpolationManager movementManager;

    private NetworkDungeonController controller = null;
    private ServerGameEvents gameEvents = null;

    // Use this for initialization
	void Start () {

        this.animation["die"].wrapMode = WrapMode.Once;
        this.animation["attack"].wrapMode = WrapMode.Once;
        this.animation["gethit"].wrapMode = WrapMode.Once;
        this.animation["idle"].wrapMode = WrapMode.Loop;
        this.animation["run"].wrapMode = WrapMode.Loop;

        if (movementManager == null) movementManager = new DelayedInterpolationManager(this.transform);

        controller = GameObject.FindGameObjectWithTag("GameController").GetComponent<NetworkDungeonController>();
        gameEvents = GameObject.FindGameObjectWithTag("GameController").GetComponent<ServerGameEvents>();

        if (controller.singlePlayer) controller.skeletonSpawned = true;
    }
	
	// Update is called once per frame
	void Update () {

        if (controller.gameOver) return;

        if (controller.singlePlayer || networkView.isMine)
        {
            StateUpdate();

            if (!(currentState == SkeletonState.Dying || currentState == SkeletonState.Dead))
            {
                if (currentState == SkeletonState.Chasing && target != null)
                {
                    movementDirection = target.transform.position - transform.position;
                    movementDirection.y = 0;
                    if (movementDirection.magnitude >= GetComponent<CombatData>().attackRange)
                    {
                        movementDirection.Normalize();
                        movementDirection *= moveSpeed;
                    }
                }
                else
                {
                    movementDirection = Vector3.zero;
                }

                if (movementDirection != Vector3.zero)
                {
                    this.transform.rotation.SetLookRotation(movementDirection);

                    this.transform.position += movementDirection * Time.deltaTime;
                }

                if (target != null)
                {
                    Vector3 lookTarget = target.transform.position;
                    lookTarget.y = this.transform.position.y;
                    this.transform.LookAt(lookTarget, Vector3.up);
                }
            }


        }
        else
        {
            movementManager.DelayInterpolateMovement();
        }

	
        AnimationUpdate();

        // TODO: Task 5 - Let the players attack monsters
        // Receive hits, respond to damage, die when appropriate

        // TODO: Task 9 - Let the monsters attack players
        // Attack only when in range, and on an interval

        // TODO: Task 11 - Scale the fight for the number of players
        // When spawning, scale abilities up - remember, do some math around this.
	}

    void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    {
        int numEvents = 0;
        int stateValue = (int)SkeletonState.Waiting;
        if (stream.isWriting)
        {
            // 1. Write state
            stateValue = (int)currentState;
            stream.Serialize(ref stateValue);
            movementManager.WriteSerializedData(stream);

            // 2. Write number of events
            // TODO: Get event count from queue
            stream.Serialize(ref numEvents);

            // 3. Write events
            // TODO: Write numEvents events
        }
        else
        {
            // 1. Read state
            stream.Serialize(ref stateValue);
            currentState = (SkeletonState)stateValue;
            movementManager.ReadSerializedData(stream);

            // 2. Read number of events
            stream.Serialize(ref numEvents);

            for (int i = 0; i < numEvents; i++)
            {
                // TODO: Read an event and process it
            }
        }
    }

    void OnNetworkInstantiate(NetworkMessageInfo info)
    {
        movementManager = new DelayedInterpolationManager(this.transform);

        controller = GameObject.FindGameObjectWithTag("GameController").GetComponent<NetworkDungeonController>();
        controller.skeletonSpawned = true;

        DontDestroyOnLoad(this.gameObject);

    }

    #region State events

    private void StateUpdate()
    {
        switch (currentState)
        {
            case SkeletonState.Waiting:
                // Can enter Chasing if we see a player (where do we get the players?)
                // Can enter Dead if the GM cheats
                if (this.GetComponent<CombatData>().currentHealth <= 0) EnterDying();
                if (UnityEngine.Random.Range(1, seekRandomizer) == seekRandomizer - 1) LookForPlayers();
                if (target != null) EnterChasing();
                break;
            case SkeletonState.Chasing:
                // Enter Attacking if we're in range and our attack cooldown is available (check CombatData)
                // Enter Waiting if we lose the player
                if (!TrackTarget())
                {
                    target = null;
                    EnterWaiting();
                }
                else
                {
                    if (TargetInRange())
                    {
                        EnterFighting();
                    }
                }
                break;
            case SkeletonState.Fighting:
                // Enter Attacking when attack is available
                if (this.GetComponent<CombatData>().CanAttack())
                {
                    if (target != null && !target.GetComponent<FPController>().dead)
                    {
                        EnterAttacking();
                    }
                    else
                    {
                        EnterWaiting();
                    }
                }
                break;
            case SkeletonState.Attacking:
                // Change to Fighting when the animation is done playing
                if (attackPlayed && !animation.IsPlaying("attack")) EnterChasing();
                break;
            case SkeletonState.Hit:
                // Change to Chasing on the next update
                if (this.GetComponent<CombatData>().currentHealth <= 0)
                {
                    EnterDying();
                } 
                else if (DateTime.Now >= stateExpiration) 
                {
                    EnterChasing();
                } 
                break;
            case SkeletonState.Dying:
                // Change to Dead on the next update
                if (DateTime.Now >= stateExpiration) EnterDead();
                break;
            case SkeletonState.Dead:
                // How did we even get here? Aren't we supposed to be destroyed?
                break;
        }
    }

    private void EnterAttacking()
    {
        print("skeleton attacking");
        this.currentState = SkeletonState.Attacking;
        this.GetComponent<CombatData>().Attack();

        // Anybody in front?
        GameObject[] players = GameObject.FindGameObjectsWithTag("Player");
        foreach (GameObject player in players)
        {
            if (!player.GetComponent<FPController>().dead)
            {
                print("viable target");
                Vector3 directionToPlayer = player.transform.position - this.transform.position;
                directionToPlayer.y = 0;
                if (Vector3.Angle(this.transform.forward, directionToPlayer) < this.GetComponent<CombatData>().sweep)
                {
                    // SLAM!
                    PlayerHitGameEvent playerHitEvent = new PlayerHitGameEvent(player.networkView.owner, player.networkView.owner, this.GetComponent<CombatData>().attackPower);
                    gameEvents.eventQueue.AddEvent(playerHitEvent);
                    return;
                }
                else
                {
                    print("target outside of sweep");
                }
            }
        }
        print("attacked but hit nothing");
    }

    private void EnterFighting()
    {
        attackPlayed = false;
        currentState = SkeletonState.Fighting;
        movementDirection = Vector3.zero;
    }

    private bool TargetInRange()
    {
        return Vector3.Distance(this.transform.position, target.transform.position) <= this.GetComponent<CombatData>().attackRange;
    }

    private bool TrackTarget()
    {
        if (target == null) return false;

        if (target.GetComponent<FPController>().dead)
        {
            print("target died");
            return false;
        }

        RaycastHit hitInfo;
        if (!Physics.Raycast(this.transform.position, target.transform.position - this.transform.position, out hitInfo))
        {
            print("lost target");
            target = null;
            return false;
        }

        if (hitInfo.transform.tag == "Player")
        {
            return true;
        }

        return false;
    }

    private void EnterWaiting()
    {
        currentState = SkeletonState.Waiting;
        movementDirection = Vector3.zero;
    }

    private void LookForPlayers()
    {
        // Look for visible players, chase the closest one
        GameObject[] players = GameObject.FindGameObjectsWithTag("Player");
        foreach (GameObject player in players)
        {
            if (!player.GetComponent<FPController>().dead)
            {
                RaycastHit hitInfo;
                if (Physics.Raycast(new Ray(this.transform.position, player.transform.position - this.transform.position), out hitInfo))
                {
                    if (hitInfo.transform.tag == "Player" && (target == null || Vector3.Distance(this.transform.position, target.transform.position) > hitInfo.distance))
                    {
                        target = hitInfo.transform.gameObject;
                    }
                }
            }
        }

    }

    private void EnterDying()
    {
        currentState = SkeletonState.Dying;
        stateExpiration = DateTime.Now.AddSeconds(5);
    }

    private void EnterChasing()
    {
        currentState = SkeletonState.Chasing;

    }

    private void EnterDead()
    {
        currentState = SkeletonState.Dead;
        Destroy(this.gameObject);
    }

    #endregion

    private void AnimationUpdate()
    {
        // idle, running, attack, gethit, die

        switch (currentState)
        {
            case SkeletonState.Waiting:
                if (!animation.IsPlaying("idle"))
                    animation.PlayQueued("idle", QueueMode.PlayNow, PlayMode.StopAll);
                break;
            case SkeletonState.Dying:
                if (!deadPlayed && !animation.IsPlaying("die"))
                {
                    deadPlayed = true;
                    animation.PlayQueued("die", QueueMode.PlayNow, PlayMode.StopAll);
                }
                break;
            case SkeletonState.Chasing:
                if (!animation.IsPlaying("run"))
                {
                    animation.PlayQueued("run", QueueMode.PlayNow, PlayMode.StopAll);
                }
                break;
            case SkeletonState.Fighting:
                if (!animation.IsPlaying("waitingforbattle"))
                {
                    animation.PlayQueued("waitingforbattle", QueueMode.PlayNow, PlayMode.StopAll);
                }
                break;
            case SkeletonState.Attacking:
                if (!attackPlayed && !animation.IsPlaying("attack"))
                {
                    attackPlayed = true;
                    animation.PlayQueued("attack", QueueMode.PlayNow, PlayMode.StopAll);
                }
                break;
            case SkeletonState.Hit:
                if (!gethitPlayed && !animation.IsPlaying("gethit"))
                {
                    gethitPlayed = true;
                    animation.PlayQueued("gethit", QueueMode.PlayNow, PlayMode.StopAll);
                }
                else if (gethitPlayed && !animation.IsPlaying("gethit") && !animation.IsPlaying("waitingforbattle"))
                {
                    animation.PlayQueued("waitingforbattle", QueueMode.PlayNow, PlayMode.StopAll);
                }
                break;
        }

    }

    internal void ReceiveHit(int damage)
    {
        this.GetComponent<CombatData>().currentHealth -= damage;
        EnterHit();
    }

    private void EnterHit()
    {
        print("skeleton hit by a player");
        gethitPlayed = false;
        currentState = SkeletonState.Hit;
        stateExpiration = DateTime.Now.AddMilliseconds(1000);
    }

    internal void Scale(int playerCount)
    {
        if (playerCount > largestPlayerCount)
        {
            largestPlayerCount = playerCount;
            this.GetComponent<CombatData>().maxHealth = 20 + 2 * (largestPlayerCount - 1);
        }
    }
}
